package org.xserver.common.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xserver.component.annotation.Alias;
import org.xserver.component.json.bean.JQueryTab;

public class ReflectionUtil {

	private static final Logger log = LoggerFactory
			.getLogger(ReflectionUtil.class);

	/**
	 * Whether found class
	 * 
	 * @param className
	 *            target class name
	 * @return if found return true, else return false
	 */
	public static boolean classFound(String className) {
		try {
			Class.forName(className);
			return true;
		} catch (Exception e) {
		}

		return false;
	}

	/**
	 * Whether found classes
	 * 
	 * @param classNames
	 *            target classes names
	 * @return if all classes found return true, else return false
	 */
	public static boolean classFound(String[] classNames) {
		try {
			if (!StringUtil.isEmpty(classNames)) {
				for (String className : classNames) {
					classFound(className);
				}
			}
			return true;
		} catch (Exception e) {
		}

		return false;
	}

	public static Class<?> getClass(String className) {
		try {
			return Class.forName(className);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Return the target bean information
	 * 
	 * @param t
	 *            target bean
	 * @return the information about all fields
	 * @throws Exception
	 */
	public static <T> String getTargetInfo(T t) {
		Field[] fields = t.getClass().getDeclaredFields();

		StringBuffer sb = new StringBuffer("[" + t.getClass().getName() + ":");

		try {
			for (Field field : fields) {
				field.setAccessible(true);
				sb.append(field.getName()).append(field.get(t)).append(", ");
			}
		} catch (Exception e) {
			log.error("getTargetInfo occur error.", e);
			return "";
		}

		return sb.substring(0, sb.length() - 2) + "]";
	}

	/**
	 * Get the specified class field as Map return
	 * 
	 * @param clazz
	 *            the specified class
	 * @return the field map
	 */
	public static <T> Map<String, Field> getFieldMap(Class<T> clazz) {
		Field[] fields = clazz.getDeclaredFields();
		Map<String, Field> fieldMap = new HashMap<String, Field>(fields.length);
		for (Field field : fields) {
			Alias alias = field.getAnnotation(Alias.class);
			String name = alias == null ? field.getName() : alias.value();
			fieldMap.put(name, field);
		}

		return fieldMap;
	}

	public static Class<?> getGenericClass(Field field) {
		Type type = field.getGenericType();
		if (type instanceof ParameterizedType) {
			ParameterizedType pt = (ParameterizedType) type;
			return (Class<?>) pt.getActualTypeArguments()[0];
		}

		return field.getType();
	}

	/**
	 * Exclude specified fields and return other fields in class, but when the
	 * exceptFields is empty or null just return a string array with zero length
	 * 
	 * @param clazz
	 * @param exceptFields
	 * @return
	 */
	public static String[] exceptFields(Class<?> clazz, String[] exceptFields) {
		if (StringUtil.isEmpty(exceptFields)) {
			return new String[] {};
		}

		Field[] fields = clazz.getDeclaredFields();

		Map<String, String> map = new HashMap<String, String>();
		for (Field field : fields) {
			map.put(field.getName(), field.getName());
		}

		for (String exceptField : exceptFields) {
			if (map.containsKey(exceptField)) {
				map.remove(exceptField);
			}
		}

		String[] includeFields = (String[]) Array.newInstance(String.class, map
				.keySet().size());
		map.keySet().toArray(includeFields);
		return includeFields;
	}

	public static Class<?>[] getClasses(String[] classes)
			throws ClassNotFoundException {
		if (!StringUtil.isEmpty(classes)) {
			List<Class<?>> list = new ArrayList<Class<?>>();
			Class<?>[] result = new Class<?>[classes.length];
			for (String clazz : classes) {
				list.add(Class.forName(clazz));
			}

			list.toArray(result);
			return result;
		}

		return null;
	}

	public static void invokeMethod(Class<?> clazz, String methodName,
			Class<?>[] parameterTypes, Object[] args, Object invoker)
			throws Exception {
		Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
		method.invoke(invoker, args);
	}

	public static String[] getMethodWithParamterTypes(Class<?> clazz,
			Class<?>[] parameterTypes) {
		Method[] methods = clazz.getDeclaredMethods();
		List<String> methodNames = new ArrayList<String>();
		for (Method method : methods) {
			if (parameterTypesEquals(method.getParameterTypes(), parameterTypes)) {
				methodNames.add(method.getName());
			}
		}

		String[] result = (String[]) Array.newInstance(String.class,
				methodNames.size());
		methodNames.toArray(result);

		return result;
	}

	public static boolean parameterTypesEquals(
			Class<?>[] declareParameterTypes, Class<?>[] invokeParameterTypes) {
		if (declareParameterTypes.length != invokeParameterTypes.length) {
			return false;
		}

		for (int i = 0; i < declareParameterTypes.length; i++) {
			if (!declareParameterTypes[i]
					.isAssignableFrom(invokeParameterTypes[i])) {
				return false;
			}
		}

		return true;
	}

	public static <T extends Annotation> Field getFieldWithAnnotation(
			Class<?> clazz, Class<T> annotation) {
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			T t = field.getAnnotation(annotation);
			if (t != null) {
				return field;
			}
		}

		return null;
	}

	public static <T extends Annotation> List<Field> getFieldsWithAnnotation(
			Class<?> clazz, Class<T> annotation) {
		Field[] fields = clazz.getDeclaredFields();

		List<Field> result = new ArrayList<Field>(fields.length);
		for (Field field : fields) {
			T t = field.getAnnotation(annotation);
			if (t != null) {
				result.add(field);
			}
		}

		if (result.size() != 0) {
			return result;
		}

		return null;
	}

	public static <T> void setFieldValue(T t, Field field, Object value)
			throws IllegalArgumentException, IllegalAccessException {
		field.setAccessible(true);
		field.set(t, value);
	}

	public static <T> Object getFieldValue(T t, Field field)
			throws IllegalArgumentException, IllegalAccessException {
		field.setAccessible(true);
		return field.get(t);
	}

	public static boolean isStatic(Field field) {
		if (Modifier.isStatic(field.getModifiers())) {
			return true;
		}

		return false;
	}
	
	public static Method[] getMethodsWithAnnotation(Class<?> clazz,
			Class<? extends Annotation> annotationClass) {
		Method[] methods = clazz.getDeclaredMethods();

		List<Method> result = new ArrayList<Method>();
		for (Method method : methods) {
			Annotation annotation = method.getAnnotation(annotationClass);
			if (annotation != null) {
				result.add(method);
			}
		}

		if (!result.isEmpty()) {
			return result.toArray(new Method[result.size()]);
		}

		return new Method[0];
	}

	public static void main(String[] args) throws IllegalArgumentException,
			IllegalAccessException {
		JQueryTab j = new JQueryTab();
		j.setContent("123");
		j.setId("yyy");
		j.setTitle("title");
		j.setI(10);
		Field[] fields = j.getClass().getDeclaredFields();

		for (Field field : fields) {
			System.out.println(getFieldValue(j, field).getClass());
		}
	}
}
